#include <set>
#include <stack>
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>
#include <xscript/xlexer.h>
#include <xscript/xprint.h>
#include <xscript/xscriptexecutor.h>

std::wstring WORD_DOT(L".");
std::wstring WORD_TRUE(L"True");
std::wstring WORD_FALSE(L"False");
boost::wregex VARIABLE_REGEX(L"^[_a-zA-Z]\\w*$");

std::set<wchar_t> XLexParser::separator{' ', '.', ',', '=', '(', ')', '[', ']', '-', '+', '*', '/', '{', '}', '"', '<', '>', '\'', ':'};
std::wstring XLexParser::multiCommentFlag = L"'''";
std::set<std::wstring> XLexParser::keywords = {
    L"let", L"function",
    L"end", L"if", L"elseif", L"else",
    L"while", L"for",
    L"break", L"continue",
    L"return", L"import",
    L"module", L"class",
    L"and", L"or", L"not", L"equal"
};

std::set<std::wstring> XLexParser::booleans = {
    L"True", L"False"
};

std::set<std::wstring> XLexParser::symbols = {
    L"=",
    L"+", L"-", L"*", L"/",
    L"(", L")",
    L",",
    L"[", L"]",
    L"{", L"}",
    L"<", L">",
    L"->",
    L":",
    L"."
};

std::set<std::wstring> XLexParser::enterBlockKeywords = {L"if", L"while", L"for", L"function"};
std::set<std::wstring> XLexParser::exitBlockKeywords = {L"end"};
std::set<std::wstring> XLexParser::enterExitBlockKeywords = {L"elseif", L"else"};

XLexParser::XLexParser() {
    blocks.push(0);
}

void XLexParser::setModuleName(const std::wstring& module) {
    currentModule = module;
}

bool XLexParser::parse(const std::wstring& input, uint32_t line) {
    auto text = boost::algorithm::trim_copy(input);
    if (boost::algorithm::starts_with(input, L"##") || input.empty())
        return true;

    if (isMultiComment && multiCommentFlag == text) {
        isMultiComment = false;
        return true;
    }

    if (boost::algorithm::starts_with(text, multiCommentFlag)) {
        isMultiComment = true;
        return true;
    }

    if (isMultiComment)
        return true;

    XTokenLine tokenLine;
    tokenLine.line = line;

    char prevCh = -1;
    bool isString = false;

    std::vector<std::wstring> sections;

    std::wstring word;
    for (const auto& ch : input) {
        if (isString && prevCh == '\\' && ch == '"')
            word += ch;
        else if (isString && ch == '"') {
            word += ch;
            sections.push_back(word);
            word.clear();
            isString = false;
        } else if (isString) {
            word += ch;
        } else if (ch == '"') {
            word += ch;
            isString = true;
        } else if (std::find(separator.begin(), separator.end(), ch) != separator.end()) {
            if (!word.empty())
                sections.push_back(word);
            if (ch != ' ') {
                std::wstring item;
                item += ch;

                sections.push_back(item);
            }
            word.clear();
        } else
            word += ch;
        prevCh = ch;
    }

    if (!word.empty())
        sections.push_back(word);

    if (sections.empty())
        return true;

    return makeToken(sections, line);
}

bool XLexParser::isInteger(const std::wstring& string) {
    for (auto ch : string)
        if (!isdigit(ch))
            return false;
    return true;
}

bool XLexParser::makeToken(const std::vector<std::wstring>& words, uint32_t line) {
    if (words.size() == 1) {
        auto word = words.at(0);

        if (blocks.empty()) {
            XScriptExecutor::getInstance()->setError(XError::XError_BadTokenError, word);
            return false;
        }

        if (contain(keywords, word)) {
            addToken(std::make_shared<XTokenKeyword>(word, line, blocks.top()), line);
            return doKeyword(word, line);
        } else {
            XScriptExecutor::getInstance()->setError(XError::XError_BadTokenError, word);
            return false;
        }
    }

    std::wstring nextWord;
    XTokenLine tokenLine;
    tokenLine.line = line;

    for (size_t index = 0; index < words.size(); index++) {
        if (index < words.size() - 1)
            nextWord = words.at(index + 1);
        else
            nextWord.clear();

        auto word = words.at(index);

        if (word == WORD_DOT) {
            std::shared_ptr<XToken> prev;
            prev = *--tokenLine.tokens.end();

            bool nextIsInteger = isInteger(nextWord);

            if (prev && prev->asInteger() && nextIsInteger) {
                std::wstring word = prev->getString() + L"." + nextWord;
                tokenLine.tokens.erase(--tokenLine.tokens.end());
                tokenLine.tokens.push_back(std::make_shared<XTokenFloat>(word, line, blocks.top()));
                index ++;
                continue;
            } else if (nextIsInteger) {
                std::wstring word = std::wstring(L".") + nextWord;
                tokenLine.tokens.push_back(std::make_shared<XTokenFloat>(word, line, blocks.top()));
                index ++;
                continue;
            }
        }

        if (contain(keywords, word)) {
            if (!doKeyword(word, line))
                return false;
            else {
                tokenLine.tokens.push_back(std::make_shared<XTokenKeyword>(word, line, blocks.top()));
                continue;
            }
        }

        if (blocks.empty()) {
            XScriptExecutor::getInstance()->setError(XError::XError_BadTokenError, word);
            return false;
        }

        if (isInteger(word))
            tokenLine.tokens.push_back(std::make_shared<XTokenInteger>(word, line, blocks.top()));
        else if (contain(symbols, word)) {
            std::wstring prev;
            if (!tokenLine.tokens.empty())
                prev = tokenLine.tokens.back()->getString();
            if (prev == L">" && word == L"=")
                tokenLine.tokens.back()->asPunctuator()->setString(L">=");
            else if (prev == L"<" && word == L"=")
                tokenLine.tokens.back()->asPunctuator()->setString(L"<=");
            else if (prev == L"-" && word == L">")
                tokenLine.tokens.back()->asPunctuator()->setString(L"->");
            else
                tokenLine.tokens.push_back(std::make_shared<XTokenPunctuator>(word, line, blocks.top()));
        } else if (word[0] == '"' && word[word.size() - 1] == '"')
            tokenLine.tokens.push_back(std::make_shared<XTokenString>(word, line, blocks.top()));
        else if (word == WORD_TRUE || word == WORD_FALSE)
            tokenLine.tokens.push_back(std::make_shared<XTokenBoolean>(word, line, blocks.top()));
        else {
            auto token = std::make_shared<XTokenVariable>(word, line, blocks.top());
            if (!isValidVariableName(word)) {
                XScriptExecutor::getInstance()->setError(XError::XError_BadTokenError, token->toString());
                return false;
            }
            tokenLine.tokens.push_back(token);
        }
    }

    if (!tokenLine.tokens.empty())
        tokenBlock.push_back(tokenLine);

    return true;
}

bool XLexParser::isValidVariableName(const std::wstring& word) {
    boost::match_results<std::wstring::const_iterator> what;
    return boost::regex_match(word, what, VARIABLE_REGEX, boost::match_default);
}

bool XLexParser::doKeyword(const std::wstring& word, uint32_t line) {
    bool enter = needEnterBlock(word);

    if (enter) {
        blocks.push(line);
    } else if (needExitBlock(word)) {
        if (blocks.empty()) {
            XScriptExecutor::getInstance()->setError(XError::XError_BadTokenError, word);
            return false;
        }
        blocks.pop();
    } else if (needExitAndEnterBlock(word)) {
        if (blocks.empty()) {
            XScriptExecutor::getInstance()->setError(XError::XError_BadTokenError, word);
            return false;
        }
        blocks.pop();
        blocks.push(line);
    }

    if (blocks.empty()) {
        XScriptExecutor::getInstance()->setError(XError::XError_BadTokenError, word);
        return false;
    }

    return true;
}

bool XLexParser::needEnterBlock(const std::wstring& keyword) {
    return enterBlockKeywords.find(keyword) != enterBlockKeywords.end();
}

bool XLexParser::needExitBlock(const std::wstring& keyword) {
    return exitBlockKeywords.find(keyword) != exitBlockKeywords.end();
}

bool XLexParser::needExitAndEnterBlock(const std::wstring& keyword) {
    return enterExitBlockKeywords.find(keyword) != enterExitBlockKeywords.end();
}

void XLexParser::addToken(std::shared_ptr<XToken> token, uint32_t line) {
    XTokenLine tokenLine;
    tokenLine.line = line;
    tokenLine.tokens.push_back(token);
    tokenBlock.push_back(tokenLine);
}
